use crate::{Solution, SolutionPair};
use std::{cmp::max, fs::read_to_string};

type TreeHeight = usize;

enum TreeNeighborDirection {
    UP,
    RIGHT,
    DOWN,
    LEFT,
}

#[derive(Debug)]
struct Trees {
    row_len: usize,
    column_len: usize,
    grid: Vec<Vec<TreeHeight>>,
}

impl Trees {
    fn new(input: &str) -> Self {
        Self {
            row_len: input.lines().count(),
            column_len: input.lines().last().unwrap().len(),
            grid: input
                .lines()
                .map(|line| {
                    line.chars()
                        .map(|tree| tree.to_digit(10).unwrap() as TreeHeight)
                        .collect::<Vec<TreeHeight>>()
                })
                .collect::<Vec<Vec<TreeHeight>>>(),
        }
    }

    fn get_tree_height(&self, row_index: usize, column_index: usize) -> Option<TreeHeight> {
        self.grid.get(row_index)?.get(column_index).copied()
    }

    fn get_single_direction_neighbors(
        &self,
        row_index: usize,
        column_index: usize,
        direction: TreeNeighborDirection,
    ) -> Vec<TreeHeight> {
        use TreeNeighborDirection as Direction;

        let mut result: Vec<TreeHeight> = Vec::new();

        'match_direction: {
            match direction {
                Direction::UP => {
                    if row_index == 0 {
                        break 'match_direction;
                    }

                    let mut current_index = row_index - 1;
                    loop {
                        result.push(self.get_tree_height(current_index, column_index).unwrap());

                        if current_index == 0 {
                            break;
                        } else {
                            current_index -= 1;
                        }
                    }
                }
                Direction::RIGHT => {
                    if column_index == self.column_len - 1 {
                        break 'match_direction;
                    }

                    let mut current_index = column_index + 1;
                    loop {
                        result.push(self.get_tree_height(row_index, current_index).unwrap());

                        if current_index == self.column_len - 1 {
                            break;
                        } else {
                            current_index += 1;
                        }
                    }
                }
                Direction::DOWN => {
                    if row_index == self.row_len - 1 {
                        break 'match_direction;
                    }

                    let mut current_index = row_index + 1;
                    loop {
                        result.push(self.get_tree_height(current_index, column_index).unwrap());

                        if current_index == self.row_len - 1 {
                            break;
                        } else {
                            current_index += 1;
                        }
                    }
                }
                Direction::LEFT => {
                    if column_index == 0 {
                        break 'match_direction;
                    }

                    let mut current_index = column_index - 1;
                    loop {
                        result.push(self.get_tree_height(row_index, current_index).unwrap());

                        if current_index == 0 {
                            break;
                        } else {
                            current_index -= 1;
                        }
                    }
                }
            };
        }

        result
    }

    fn visit<F>(&self, mut visitor: F)
    where
        F: FnMut(usize, usize),
    {
        self.grid.iter().enumerate().for_each(|(row_index, row)| {
            row.iter().enumerate().for_each(|(column_index, _)| {
                visitor(row_index, column_index);
            });
        });
    }
}

fn solve_part1(input: &str) -> usize {
    let mut visible_tree_count: usize = 0;

    let trees = Trees::new(input);

    trees.visit(|row_index, column_index| {
        if row_index == 0
            || row_index == trees.row_len - 1
            || column_index == 0
            || column_index == trees.column_len - 1
        {
            visible_tree_count += 1;
            return;
        }

        let current_tree_height = trees.get_tree_height(row_index, column_index).unwrap();

        for direction in [
            TreeNeighborDirection::UP,
            TreeNeighborDirection::RIGHT,
            TreeNeighborDirection::DOWN,
            TreeNeighborDirection::LEFT,
        ] {
            if trees
                .get_single_direction_neighbors(row_index, column_index, direction)
                .iter()
                .all(|v| *v < current_tree_height)
            {
                visible_tree_count += 1;
                break;
            }
        }
    });

    visible_tree_count
}

fn solve_part2(input: &str) -> usize {
    let trees = Trees::new(input);
    let mut max_score: usize = 0;

    trees.visit(|row_index, column_index| {
        let mut current_score: usize = 1;
        let current_tree_height = trees.get_tree_height(row_index, column_index).unwrap();

        for direction in [
            TreeNeighborDirection::UP,
            TreeNeighborDirection::RIGHT,
            TreeNeighborDirection::DOWN,
            TreeNeighborDirection::LEFT,
        ] {
            let neighbors =
                trees.get_single_direction_neighbors(row_index, column_index, direction);
            let mut tree_count = 0;
            let mut hightest = 0;
            for height in neighbors {
                if hightest <= height {
                    hightest = height;
                } else {
                    break;
                }

                tree_count += 1;
                if height > current_tree_height {
                    break;
                }
            }

            // current_score *= if tree_count != 0 { tree_count } else { 1 };
            current_score *= tree_count;
        }

        // DEBUG:
        println!("({}, {}) -> {}", row_index, column_index, current_score);
        max_score = max(max_score, current_score);
    });

    // TODO:
    max_score
    // 486540
}

pub fn solve() -> SolutionPair {
    let input = read_to_string("input/day08.txt").unwrap();

    let sol1 = solve_part1(&input);
    let sol2 = solve_part2(&input);

    (Solution::from(sol1), Solution::from(sol2))
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::etc::utils;

    #[test]
    fn construct_trees() {
        let input = utils::to_multiline_string(vec!["30373", "25512", "65332", "33549", "35390"]);
        dbg!(Trees::new(&input));
    }

    #[test]
    fn tree_neighbors() {
        let input = utils::to_multiline_string(vec!["30373", "25512", "65332", "33549", "35390"]);
        let trees = Trees::new(&input);
        dbg!(trees.get_single_direction_neighbors(2, 0, TreeNeighborDirection::LEFT));
    }

    #[test]
    fn part1() {
        let input = utils::to_multiline_string(vec!["30373", "25512", "65332", "33549", "35390"]);
        assert_eq!(solve_part1(&input), 21);
    }

    #[test]
    fn part2() {
        let input = utils::to_multiline_string(vec!["30373", "25512", "65332", "33549", "35390"]);
        dbg!(solve_part2(&input));
    }
}
